﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Nabaztag.cs" company="Jim Liddell">
//   Copyright © Jim Liddell. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace TeamBuildBunny
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Xml.Serialization;

    #endregion

    /// <summary>
    /// Represents a Nabaztag bunny.
    /// </summary>
    public class Nabaztag
    {
        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Nabaztag"/> class.
        /// </summary>
        public Nabaztag()
        {
            // TODO: Pull this out into configuration
            this.ApiUrl = "http://api.nabaztag.com/vl/FR/api.jsp";
            this.Identity = new NabaztagIdentity();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the URL to which API calls should be sent.
        /// </summary>
        public string ApiUrl
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the identity of the <see cref="Nabaztag"/>.
        /// </summary>
        public NabaztagIdentity Identity
        {
            get;
            set;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The move ears.
        /// </summary>
        /// <param name="posLeft">
        /// The pos left.
        /// </param>
        /// <param name="posRight">
        /// The pos right.
        /// </param>
        public void MoveEars(int posLeft, int posRight)
        {
            NabaztagMessage message = this.CreateMessage();
            message.PosLeft = posLeft;
            message.PosRight = posRight;
            message.SendEars = true;

            this.SendMessage(message);
        }

        /// <summary>
        /// Sends the specified <paramref name="text"/> to the <see cref="Nabaztag"/>.
        /// </summary>
        /// <param name="text">
        /// The text to send.
        /// </param>
        public void Play(string text)
        {
            this.SendMessage(text);
        }

        /// <summary>
        /// Sends the specified <paramref name="text"/> to the <see cref="Nabaztag"/>.
        /// </summary>
        /// <param name="text">
        /// The text to send.
        /// </param>
        /// <param name="posLeft">
        /// The position of the left ear.
        /// </param>
        /// <param name="posRight">
        /// The position of the right ear.
        /// </param>
        /// <returns>
        /// The message sent.
        /// </returns>
        public NabaztagMessage SendMessage(string text, int posLeft, int posRight)
        {
            NabaztagMessage message = this.CreateMessage();
            message.MessageText = text;
            message.PosLeft = posLeft;
            message.PosRight = posRight;
            message.SendEars = true;

            this.SendMessage(message);

            return message;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a default message prototype.
        /// </summary>
        /// <returns>
        /// A default message prototype.
        /// </returns>
        private NabaztagMessage CreateMessage()
        {
            NabaztagMessage message = new NabaztagMessage(this.Identity);
            message.SendEars = true;
            message.TimeToLiveSeconds = 30;

            return message;
        }

        /// <summary>
        /// Sends the specified <paramref name="text"/> to the <see cref="Nabaztag"/>.
        /// </summary>
        /// <param name="text">
        /// The text to send.
        /// </param>
        private void SendMessage(string text)
        {
            NabaztagMessage message = this.CreateMessage();
            message.MessageText = text;

            this.SendMessage(message);
        }

        /// <summary>
        /// Sends the specified <paramref name="message"/> to the <see cref="Nabaztag"/>.
        /// </summary>
        /// <param name="message">
        /// The message to send.
        /// </param>
        /// <returns>
        /// The HTTP response string.
        /// </returns>
        private string SendMessage(NabaztagMessage message)
        {
            Uri uri = new Uri(this.ApiUrl + "?" + message);

            WebRequest request = WebRequest.Create(uri);
            WebResponse response = request.GetResponse();

            StreamReader reader = new StreamReader(response.GetResponseStream());
            string responseString = reader.ReadToEnd();

            response.Close();

            return responseString;
        }

        #endregion

        /// <summary>
        /// Provides access to a list of common voices.
        /// </summary>
        public static class Voices
        {
            #region Constants and Fields

            /// <summary>
            /// The voices.
            /// </summary>
            private static IList<Voice> voices;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes static members of the <see cref="Voices"/> class.
            /// </summary>
            static Voices()
            {
                voices = new List<Voice>
                    {
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-US"), Command = "US-Billye" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("hi-IN"), Command = "IN-Nima" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("ko-KR"), Command = "KR-Choe" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("da-DK"), Command = "DK-Karen" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("fr-FR"), Command = "FR-Gertrude" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("it-IT"), Command = "IT-Ugo" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("it-IT"), Command = "IT-Assunta" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("es-ES"), Command = "ES-Baltasar" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("fr-FR"), Command = "FR-Maxence" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("nl-NL"), Command = "NL-Renate" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("sv-SE"), Command = "SE-Hjalmar" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("fr-BE"), Command = "BE-Minna" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-US"), Command = "US-Clarence" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-GB"), Command = "UK-Mistermuggles" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("eu-ES"), Command = "ES-Dunixe" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-ZA"), Command = "ZA-Wilbur" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-US"), Command = "US-Darleen" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("sv-SE"), Command = "SE-Maj" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("no"), Command = "NO-Cora" }, 
                        // originally no-NO - which is not valid
                        new Voice { Culture = CultureInfo.GetCultureInfo("de-DE"), Command = "DE-Otto" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("nl-BE"), Command = "BE-Hendrik" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("ru-RU"), Command = "RU-Bella" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("sv-SE"), Command = "SE-Selma" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("es-ES"), Command = "ES-Alfonsina" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("ar-EG"), Command = "EG-Nayla" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("es-ES"), Command = "ES-Rosalia" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("pt-BR"), Command = "BR-Lygia" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("ja-JP"), Command = "JP-Tamura" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("fr-CA"), Command = "CA-Felix" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-AU"), Command = "AU-Colleen" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("ca-ES"), Command = "ES-Bertrana" }, 
                        // new Voice { Culture = CultureInfo.GetCultureInfo("en-IN"), Command = "IN-Sangeeta" }, // not valid
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-GB"), Command = "UK-Shirley" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("zh-CN"), Command = "CN-Pan" }, 
                        // originally zh-cnm-CN - which is not valid
                        new Voice { Culture = CultureInfo.GetCultureInfo("pt-PT"), Command = "PT-Celia" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("nl-NL"), Command = "NL-Max" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-GB"), Command = "UK-Leonard" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("ar-EG"), Command = "EG-Nabil" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-US"), Command = "US-Bethany" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("fr-FR"), Command = "FR-Anastasie" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-GB"), Command = "UK-Penelope" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-US"), Command = "US-Lilian" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("tr-TR"), Command = "TR-Asli" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("no"), Command = "NO-Sigrid" }, 
                        // originally no-NO - which is not valid
                        new Voice { Culture = CultureInfo.GetCultureInfo("nl-NL"), Command = "NL-Femke" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-AU"), Command = "AU-Jon" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("da-DK"), Command = "DK-Pia" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("zh-CN"), Command = "YUE-Baibo" }, 
                        // originally zh-YUE - which is not valid
                        new Voice { Culture = CultureInfo.GetCultureInfo("fr-FR"), Command = "FR-Archibald" }, 
                        // new Voice { Culture = CultureInfo.GetCultureInfo("gr-GR"), Command = "GR-Antonis" }, // not valid
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-US"), Command = "US-Liberty" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("es-ES"), Command = "ES-Emilia" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("th-TH"), Command = "TH-Boon-mee" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-IE"), Command = "IE-Orla" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("en-GB"), Command = "UK-Edwin" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("cs-CZ"), Command = "CZ-Zdenech" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("pl-PL"), Command = "PL-Ignacy" }, 
                        new Voice { Culture = CultureInfo.GetCultureInfo("fr-CA"), Command = "CA-Antonine" }
                    };
            }

            #endregion

            #region Public Methods

            /// <summary>
            /// The get cultures.
            /// </summary>
            /// <returns>
            /// </returns>
            public static IEnumerable<CultureInfo> GetCultures()
            {
                return voices.Select(v => v.Culture).Distinct();
            }

            /// <summary>
            /// Gets the default voice for the current culture, or the first voice if none exist for the current culture.
            /// </summary>
            /// <returns>
            /// The default voice for the current culture, or the first voice if none exist for the current culture.
            /// </returns>
            public static Voice GetDefault()
            {
                CultureInfo info = CultureInfo.CurrentCulture;

                Voice voice = GetVoices(info).FirstOrDefault();

                if (voice == null)
                {
                    // no voices found for current culture, just return first voice
                    return voices.First();
                }

                // return first voice found for the current culture
                return voice;
            }

            /// <summary>
            /// The get voices.
            /// </summary>
            /// <param name="culture">
            /// The culture.
            /// </param>
            /// <returns>
            /// </returns>
            public static IEnumerable<Voice> GetVoices(CultureInfo culture)
            {
                return voices.Where(v => v.Culture.Equals(culture)).ToList();
            }

            /// <summary>
            /// Fills in the <see cref="Voice.Culture"/> property for the specified <see cref="Voice"/>.
            /// </summary>
            /// <param name="voice">
            /// The voice.
            /// </param>
            public static void ResolveCultureForVoice(Voice voice)
            {
                voice.Culture = voices.Where(v => v.Command == voice.Command).Single().Culture;
            }

            #endregion
        }

        /// <summary>
        /// The voice.
        /// </summary>
        public class Voice
        {
            #region Constants and Fields

            /// <summary>
            /// The culture for the voice.
            /// </summary>
            private CultureInfo culture;

            #endregion

            #region Properties

            /// <summary>
            /// Gets or sets the command for the voice.
            /// </summary>
            public string Command
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets the culture for the voice.
            /// </summary>
            [XmlIgnore]
            public CultureInfo Culture
            {
                get
                {
                    if (this.culture == null)
                    {
                        Voices.ResolveCultureForVoice(this);
                    }

                    return this.culture;
                }

                set
                {
                    this.culture = value;
                }
            }

            #endregion

            #region Public Methods

            /// <summary>
            /// The to string.
            /// </summary>
            /// <returns>
            /// The to string.
            /// </returns>
            public override string ToString()
            {
                return this.Command.Substring(this.Command.IndexOf('-') + 1);
            }

            public override bool Equals(object obj)
            {
                var voice = obj as Voice;

                if (voice == null)
                {
                    return false;
                }

                if (!voice.Command.Equals(this.Command))
                {
                    return false;
                }

                if (!voice.Culture.Equals(this.Culture))
                {
                    return false;
                }

                return true;
            }

            #endregion
        }
    }
}